<template>
  <t-button @click="$toggleTheme()">切换主题</t-button>&nbsp;
  <t-button variant="outline" icon="download" @click="downloadConversation">
    下载对话
  </t-button>&nbsp;
  <t-button variant="outline" icon="share" @click="shareConversation">
    分享对话
  </t-button>
  <br/>
  <br/>
  <t-chat ref="chatRef" layout="single" style="height: 600px" :clear-history="chatList.length > 0 && !isStreamLoad"
    @clear="clearConfirm">
    <template v-for="(item, index) in chatList" :key="index">
      <t-chat-item :avatar="item.avatar" :name="item.name" :role="item.role" :datetime="item.datetime"
        :text-loading="index === 0 && loading">
        <template #content>
          <div :class="['chat-bubble', item.role === 'user' ? 'user-bubble' : 'assistant-bubble']"
            v-html="item.content"></div>
        </template>
        <template v-if="!isStreamLoad" #actions>
          <t-chat-action :is-good="isGood[index]" :is-bad="isBad[index]" :content="item.content"
            @operation="(type, { e }) => handleOperation(type, { e, index })" :class="{ 
          'active-good': isGood[index],
          'active-bad': isBad[index]
        }" />
        </template>
      </t-chat-item>
    </template>
    <template #footer>
      <t-chat-input :stop-disabled="isStreamLoad" @send="inputEnter" @stop="onStop"> </t-chat-input>
    </template>
  </t-chat>
  <!-- 反馈表单对话框 -->
  <t-dialog v-model:visible="showFeedbackForm" title="反馈信息" :mask-closable="false" :show-close="false" width="500px">
    <div>
      <p>您的操作是：<strong>{{ feedbackType === 'good' ? '点赞' : '点踩' }}</strong></p>
      <p>您对这条消息的评价是：</p>
      <t-textarea v-model="feedbackContent" placeholder="请输入您的反馈（可选）" :rows="4" />
    </div>
    <template #footer>
      <t-button @click="closeFeedbackForm">取消</t-button>
      <t-button type="primary" @click="submitFeedback">提交</t-button>
    </template>
  </t-dialog>
</template>
<script setup>
import { ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { saveAs } from 'file-saver'; 

// 反馈表单相关的状态
const showFeedbackForm = ref(false);
const feedbackType = ref(null); // 'good' 或 'bad'
const currentFeedbackIndex = ref(null);
const feedbackContent = ref('');

// 滚动到底部
/**
 * Scrolls the chat window to the bottom smoothly.
 * This function is intended to be called when new messages are added
 * to ensure that the latest content is visible without requiring user
 * interaction.
 */
const backBottom = () => {
  chatRef.value.scrollToBottom({
    behavior: 'smooth',
  });
};

// 倒序渲染
const chatList = ref([
  {
    content: `模型由 <span>hunyuan</span> 变为 <span>GPT4</span>`,
    role: 'model-change',
  },
  {
    avatar: 'https://www.damon-liu.cn/wp-content/uploads/2020/09/userImg001-1.png',
    name: 'AI小智',
    datetime: '今天16:38',
    content: '它叫 McMurdo Station ATM，是美国富国银行安装在南极洲最大科学中心麦克默多站的一台自动提款机。',
    role: 'assistant',
  },
  {
    avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
    name: '自己',
    datetime: '今天16:38',
    content: '南极的自动提款机叫什么名字？',
    role: 'user',
  },
]);

// 初始化 isGood 和 isBad 数组
const isGood = ref([]);
const isBad = ref([]);

// 初始化 isGood 和 isBad 数组
const initializeFeedbackStates = () => {
  isGood.value = chatList.value.map(() => false);
  isBad.value = chatList.value.map(() => false);
};

// 调用初始化方法
initializeFeedbackStates();

const fetchCancel = ref(null);
const loading = ref(false);
const isStreamLoad = ref(false);
const chatRef = ref(null);

/**
 * Clears the confirmation by resetting the chat list to an empty array.
 * This function is intended to be used internally within the component to manage the state of the chat list.
 */
const clearConfirm = function () {
  chatList.value = [];
  initializeFeedbackStates();
};

/**
 * Handles the stop event for a fetch operation.
 * If a fetch cancellation function is available, it aborts the ongoing fetch,
 * sets loading state to false, and updates the stream load status.
 */
const onStop = function () {
  if (fetchCancel.value) {
    fetchCancel.value.abort();
    loading.value = false;
    isStreamLoad.value = false;
  }
};

/**
 * Handles different types of operations based on the provided type and options.
 * 增加反馈表单的触发逻辑
 */
 const handleOperation = function (type, options) {
  const { index } = options;
  if (type === 'good') {
    isGood.value[index] = !isGood.value[index];
    if (isGood.value[index]) {
      isBad.value[index] = false;
      // 打开反馈表单
      feedbackType.value = 'good';
      currentFeedbackIndex.value = index;
      showFeedbackForm.value = true;
    }
  } else if (type === 'bad') {
    isBad.value[index] = !isBad.value[index];
    if (isBad.value[index]) {
      isGood.value[index] = false;
      // 打开反馈表单
      feedbackType.value = 'bad';
      currentFeedbackIndex.value = index;
      showFeedbackForm.value = true;
    }
  } else if (type === 'replay') {
    const userQuery = chatList.value[index + 1].content;
    inputEnter(userQuery);
  }
};

/**
 * 提交反馈表单
 */
 const submitFeedback = () => {
  if (currentFeedbackIndex.value === null) {
    MessagePlugin.error('未找到对应的消息 ！');
    return;
  }

  // 显示固定成功消息
  MessagePlugin.info('提交成功，我们将根据你的反馈对后面的内容进行调整 ！');

  // 关闭反馈表单
  showFeedbackForm.value = false;
};

/**
 * 关闭反馈表单
 */
const closeFeedbackForm = () => {
  showFeedbackForm.value = false;
};

/**
 * Handles the asynchronous processing of user input data.
 * Sends the input message to a server via Server-Sent Events (SSE)
 * and updates the chat list with the server's response.
 *
 * @param {string} inputValue - The message content entered by the user.
 */
const handleData = async (inputValue) => {
  loading.value = true;
  isStreamLoad.value = true;
  const lastItem = chatList.value[0];
  const messages = [{
    role: 'user',
    content: inputValue,
  }];
  fetchSSE(messages, {
    success(result) {
      loading.value = false;
      const { data } = result;
      lastItem.content += data?.delta?.content;
    },
    complete(isOk, msg) {
      if (!isOk || !lastItem.content) {
        lastItem.role = 'error';
        lastItem.content = msg;
      }
      // 控制终止按钮
      isStreamLoad.value = false;
      loading.value = false;
    },
    cancel(cancel) {
      fetchCancel.value = cancel;
    },
  });
};

/**
 * Handles the input when the enter key is pressed.
 * If the stream is loading, it does nothing.
 * If the input value is empty, it also does nothing.
 * Otherwise, it creates a user message object and prepends it to the chat list.
 * It then creates an assistant placeholder message object and prepends it to the chat list.
 * Finally, it calls the handleData function with the input value.
 * @param {string} inputValue - The value of the input field when enter is pressed.
 */
const inputEnter = function (inputValue) {
  if (isStreamLoad.value) {
    return;
  }
  if (!inputValue) return;
  const params = {
    avatar: 'https://tdesign.gtimg.com/site/avatar.jpg',
    name: '自己',
    datetime: new Date().toDateString(),
    content: inputValue,
    role: 'user',
  };
  chatList.value.unshift(params);
  // 空消息占位
  const params2 = {
    avatar: 'https://www.damon-liu.cn/wp-content/uploads/2020/09/userImg001-1.png',
    name: 'AI小智',
    datetime: new Date().toDateString(),
    content: '',
    role: 'assistant',
  };
  chatList.value.unshift(params2);
  isGood.value.unshift(false);
  isBad.value.unshift(false);
  handleData(inputValue);
};

// 解析SSE数据
/**
 * Asynchronously fetches Server-Sent Events (SSE) for chat completions.
 *
 * @param {Array} messages - The list of messages to be sent for chat completion.
 * @param {Object} options - Configuration options including callbacks.
 * @param {Function} options.success - Callback for successful data reception.
 * @param {Function} options.fail - Callback for failure scenarios.
 * @param {Function} options.complete - Callback to indicate completion, regardless of success or failure.
 * @param {Function} [options.cancel] - Optional callback to cancel the fetch request.
 * @returns {Promise} A promise that resolves when the SSE stream is closed or an error occurs.
 */
const fetchSSE = async (messages, options) => {
  const { success, fail, complete, cancel } = options;
  const controller = new AbortController();
  const { signal } = controller;
  cancel?.(controller);
  // your-api-key
  const apiKey = 'sk-6R0hq8U7v3bSbT1u41Lp6kPRwAgf9wnW73WgvSC7WUI73eRO';
  const responsePromise = fetch('/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer${apiKey ? ` ${apiKey}` : ''}`,
    },
    body: JSON.stringify({
      messages, // 消息列表
      model: 'hunyuan-pro', // 模型
      stream: true, // 流式
    }),
    signal,
  }).catch((e) => {
    const msg = e.toString() || '流式接口异常';
    complete?.(false, msg);
    return Promise.reject(e); // 确保错误能够被后续的.catch()捕获
  });

  responsePromise
    .then((response) => {
      if (!response?.ok) {
        complete?.(false, response.statusText);
        fail?.();
        throw new Error('Request failed'); // 抛出错误以便链式调用中的下一个.catch()处理
      }
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      if (!reader) throw new Error('No reader available');

      const bufferArr = [];
      let dataText = ''; // 记录数据
      const event = { type: null, data: null };

      async function processText({ done, value }) {
        if (done) {
          complete?.(true);
          return Promise.resolve();
        }
        const chunk = decoder.decode(value);
        const buffers = chunk.toString().split(/\r?\n/);
        bufferArr.push(...buffers);
        const i = 0;
        while (i < bufferArr.length) {
          const line = bufferArr[i];
          if (line) {
            dataText += line;
            const response = line.slice(6);
            if (response === '[DONE]') {
              event.type = 'finish';
              dataText = '';
            } else {
              const choices = JSON.parse(response.trim())?.choices?.[0];
              if (choices.finish_reason === 'stop') {
                event.type = 'finish';
                dataText = '';
              } else {
                event.type = 'delta';
                event.data = choices;
              }
            }
          }
          if (event.type && event.data) {
            const jsonData = JSON.parse(JSON.stringify(event));
            // debugger;
            success(jsonData);
            event.type = null;
            event.data = null;
          }
          bufferArr.splice(i, 1);
        }
        return reader.read().then(processText);
      }

      return reader.read().then(processText);
    })
    .catch(() => {
      // 处理整个链式调用过程中发生的任何错误
      fail?.();
    });
};

/**
 * 下载对话记录
 */
 const downloadConversation = () => {
  const conversation = chatList.value
    .map((msg) => {
      const time = new Date(msg.datetime).toLocaleString();
      // 去除HTML标签，确保文本格式清晰
      const content = msg.content.replace(/<[^>]+>/g, '');
      return `[${time}] ${msg.name} (${msg.role}): ${content}`;
    })
    .reverse() // 反转以按时间顺序排列
    .join('\n\n');

  const blob = new Blob([conversation], { type: 'text/plain;charset=utf-8' });
  saveAs(blob, 'conversation.txt');
};

/**
 * 分享对话记录
 */
const shareConversation = async () => {
  const conversation = chatList.value
    .map((msg) => {
      const time = new Date(msg.datetime).toLocaleString();
      // 去除HTML标签，确保文本格式清晰
      const content = msg.content.replace(/<[^>]+>/g, '');
      return `[${time}] ${msg.name} (${msg.role}): ${content}`;
    })
    .reverse() // 反转以按时间顺序排列
    .join('\n\n');

  if (navigator.share) {
    try {
      await navigator.share({
        title: '对话记录',
        text: conversation,
      });
      Message.success('分享成功！');
    } catch (error) {
      Message.error('分享失败或被取消');
      console.error('分享错误:', error);
    }
  } else if (navigator.clipboard) {
    try {
      await navigator.clipboard.writeText(conversation);
      Message.success('对话已复制到剪贴板');
    } catch (error) {
      Message.error('复制到剪贴板失败');
      console.error('剪贴板错误:', error);
    }
  } else {
    Message.error('您的浏览器不支持分享功能');
  }
};

</script>
<style lang="less" scoped>
/* 聊天气泡的通用样式 */
.chat-bubble {
  max-width: 70%;
  padding: 10px 15px;
  border-radius: 15px;
  margin-bottom: 10px;
  word-wrap: break-word;
}

/* 用户消息气泡 */
.user-bubble {
  background-color: #e1ffc7; /* 浅绿色 */
  align-self: flex-end;
  border-top-right-radius: 0;
}

/* 助手消息气泡 */
.assistant-bubble {
  background-color: #f1f0f0; /* 浅灰色 */
  align-self: flex-start;
  border-top-left-radius: 0;
}

/* 按钮激活状态样式 */
.active-good {
  color: #007bff; /* 赞按钮选中时的颜色，如蓝色 */
}

.active-bad {
  color: #dc3545; /* 踩按钮选中时的颜色，如红色 */
}

/* 调整聊天底部布局 */
.chat-footer {
  display: flex;
  align-items: center;
}

.footer-actions {
  display: flex;
  gap: 8px;
  margin-left: 16px;
}
</style>
